//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2005
//
// Transliteration utility.
//

# include "StdAfx.h"
# include "Transliteration.h"
# include "Log.h"
# include "Wrappers.h"
# include "ParseHelpers.h"

CTransliterator::CTransliterator (const char * _pStart, const char * _pEnd) {
	for (size_t c = 1; c < 256; ++ c) {
		m_nodes [c].strReplacement.assign (1, c);
		m_nodes [c].pNextNodes = NULL;
	}

	m_wLangId = 0;
	m_strName = "Unknown";

	typedef std::list<std::pair<string_t, string_t> > substitutions_t;
	substitutions_t substitutions;
	substitutions_t tests;

	bool bData = false;
	const char * pLine = _pStart;
	do {
		const char * const pNextLine = FindNextLine (pLine, _pEnd);
		const size_t cLineLen = GetLineLen (pLine, pNextLine);

		if (cLineLen > 0 && pLine [0] != ';') {
			if (! bData) {
				//
				// Parse header.
				//

				if (HasPrefix (pLine, pNextLine, ".TRANSLITERATION", 16)) {
					;
				} else if (HasPrefix (pLine, pNextLine, ".NAME", 5)) {
					// Omit leading spaces.
					const char * p = pLine + 5;
					while (p < pNextLine && (* p == ' ' || * p == '\t'))
						++ p;

					m_strName.assign (p, GetLineLen (p, pNextLine));
				} else if (HasPrefix (pLine, pNextLine, ".LANGID", 7)) {
					// Omit leading spaces.
					const char * p = pLine + 7;
					while (p < pNextLine && (* p == ' ' || * p == '\t'))
						++ p;

					unsigned long ul = 0;
					if (! ParseDecU (p, pNextLine, & p, ul))
						ReportWarning ("Can't parse value of .LANGID");
					m_wLangId = ul;
				} else if (HasPrefix (pLine, pNextLine, ".TEST", 5)) {
					// Omit leading spaces.
					const char * p = pLine + 5;
					while (p < pNextLine && (* p == ' ' || * p == '\t'))
						++ p;

					// Extract input string to test.
					const char * pFrom = p;
					while (p < pNextLine && (* p != ' ' && * p != '\t'))
						++ p;
					const char * pFromEnd = p;

					// Omit spaces.
					while (p < pNextLine && (* p == ' ' || * p == '\t'))
						++ p;

					// Extract result string.
					const char * pTo = p;
					const size_t cToLen = GetLineLen (pTo, pNextLine);

					tests.push_back (substitutions_t::value_type ());
					tests.back ().first.assign (pFrom, pFromEnd);
					tests.back ().second.assign (pTo, cToLen);
				} else if (HasPrefix (pLine, pNextLine, ".DATA", 5))
					bData = true;
				else
					ReportWarning ("Unknown definition.");
			} else {
				//
				// Parse substitution pair.
				//

				// Omit leading spaces.
				const char * p = pLine;
				while (p < pNextLine && (* p == ' ' || * p == '\t'))
					++ p;

				// Extract string to search for.
				const char * pFrom = p;
				while (p < pNextLine && (* p != ' ' && * p != '\t'))
					++ p;
				const char * const pFromEnd = p;

				if (pFromEnd >= pNextLine) {
					substitutions.push_back (substitutions_t::value_type ());
					substitutions.back ().first.assign (pFrom, GetLineLen (pFrom, pNextLine));
				} else {
					// Omit spaces.
					while (p < pNextLine && (* p == ' ' || * p == '\t'))
						++ p;

					// Extract replacement string.
					const char * const pTo = p;
					const size_t cToLen = GetLineLen (pTo, pNextLine);

					substitutions.push_back (substitutions_t::value_type ());
					substitutions.back ().first.assign (pFrom, pFromEnd);
					if (cToLen > 0)
						substitutions.back ().second.assign (pTo, cToLen);
				}
			}
		}

		pLine = pNextLine;
	} while (pLine < _pEnd);

	//
	// Compile the state machine.
	//
	substitutions_t::iterator i;
	for (i = substitutions.begin (); i != substitutions.end (); ++ i) {
		// NOTE: substitutions are sorted, so prefix keys are first.
		const char * p = i->first.c_str ();
		CNode * pNode = m_nodes + static_cast<BYTE> (* p);
		++ p;
		while (* p) {
			if (! pNode->pNextNodes)
				pNode->pNextNodes = new CNode [256];

			pNode = pNode->pNextNodes + static_cast<BYTE> (* p);
			++ p;
		}
		pNode->strReplacement.swap (i->second);
	}

	//
	// Make tests.
	//
	for (i = tests.begin (); i != tests.end (); ++ i) {
		string_t strResult;
		Do (i->first.c_str (), i->first.c_str () + i->first.size (), strResult);
		if (! (strResult == i->second))
			ReportWarning ("From: '%s' To: '%s' Result: '%s'", i->first.c_str (), i->second.c_str (), strResult.c_str ());
	}

//	if (! IsLogEmpty ())
//		ShowMessageLog ();
}

void CTransliterator::Do (const char * _strIn, const char * _strInEnd, string_t & _strOut) const {
	if (_strIn == NULL || _strIn == _strInEnd)
		return;

	assert (_strIn < _strInEnd);

	// Predict output size (for speed optimization).
	std::string strOut (_strOut.c_str (), _strOut.size ());
	strOut.reserve (_strOut.size () + _strInEnd - _strIn + 1);

	const char * p = _strIn;
	do {
		// Go to the start state.
		const CNode * pNode = m_nodes + static_cast<BYTE> (* p);

		if (! pNode->pNextNodes) {
			// NOTE: Speed optimization.
			strOut.append (pNode->strReplacement.c_str (), pNode->strReplacement.size ());
			++ p;
			continue;
		}

		const string_t * pstrReplacement = NULL;
		const char * p2 = p;
		while (p2 < _strInEnd) {
			//
			// In transient state.
			//

			++ p2;

			// Save candidate for replacement.
			if (! pNode->strReplacement.empty ()) {
				pstrReplacement = & (pNode->strReplacement);
				p = p2;
			}

			if (! pNode->pNextNodes)
				break;

			// Shift state.
			pNode = pNode->pNextNodes + static_cast<BYTE> (* p2);
		}

		//
		// In terminal state.
		//

		if (pstrReplacement)
			strOut.append (pstrReplacement->c_str (), pstrReplacement->size ());
	} while (p < _strInEnd);

	_strOut = strOut;
}

///////////////////////////////////////////

CTransliteration g_Transliteration;

CTransliteration::~CTransliteration () {
	for (size_t c = 0; c < m_transliterators.size (); ++ c)
		delete m_transliterators [c];
}

void CTransliteration::Load () {
	if (m_bLoaded)
		return;
	m_bLoaded = true;

	// Get folder with mapedit.exe.
	char strProcessName [MAX_PATH + 1];
	::GetModuleFileName (NULL, strProcessName, MAX_PATH);
	const char * const pPathEnd = ::strrchr (strProcessName, '\\');

	string_t strFilter;
	if (pPathEnd)
		strFilter.assign ((const char *) strProcessName, pPathEnd + 1);
	strFilter.append ("*.dat");

	WIN32_FIND_DATA fd;
	const HANDLE hFind = ::FindFirstFile (strFilter.c_str (), & fd);
	if (hFind == INVALID_HANDLE_VALUE)
		return;

	do {
		if (pPathEnd)
			strFilter.assign ((const char *) strProcessName, pPathEnd + 1);
		strFilter.append (fd.cFileName);

		//
		// Open file.
		//

		const CFileForRead hFile (strFilter.c_str ());
		if (! hFile.IsValid ()) {
			// TODO:
		} else {
			const size_t cSize = ::GetFileSize (hFile, NULL);
			char * const pData = new char [cSize];
			CByteArrayHolder bah (reinterpret_cast<BYTE *> (pData));

			DWORD dwRead = 0;
			::ReadFile (hFile, pData, cSize, & dwRead, NULL);
			if (dwRead < cSize) {
				// TODO:
			} else {
				if (HasPrefix (pData, pData + cSize, ".TRANSLITERATION", 16))
					m_transliterators.push_back (new CTransliterator (pData, pData + cSize));
			}
		}
	} while (::FindNextFile (hFind, & fd));

	::FindClose (hFind);
}
